% def get_node_kind(mnemonic)
%   return "#{mnemonic.gsub('.', '_').upcase}"
% end
%
% def get_format_name(mnemonic)
%   return "#{mnemonic.gsub('.', '_').upcase}" + "_FORMATS"
% end
%
% def insn2node(insn)
%   mnemonic = insn.mnemonic.split('.')
%   return mnemonic.map{|el| el == '64' ? 'Wide' : el.capitalize}.join()
% end
%
% def is_VReg(name)
%     if name ==  :v
%        return true
%     end
% end
%
% def is_Acc(name)
%    if name ==  :acc
%        return true
%     end
% end
%
% def is_Imm(name)
%    if name ==  :imm
%        return true
%     end
% end
%
% def is_Id(name)
%     if %i[method_id type_id field_id string_id stringId callsite_id literalarray_id].include?(name)
%        return true
%     end
% end
%
% def get_operand_type(name, name_tmp, insn)
%   if is_VReg (name_tmp)
%     return 'uint16_t'
%   elsif is_Imm(name_tmp)
%     if insn.properties.include? 'jump'
%       return "std::string"
%     end
%     if insn.sig.include? 'imm:f64'
%       return 'double'
%     end
%       return 'int64_t'
%   elsif is_Id(name_tmp)
%     return 'std::string'
%   else
%     return nil
%   end
% end
%
% def get_operands(sig)
%     return [] unless sig.include? ' '
%     _, operands = sig.match(/(\S+) (.+)/).captures
%     operands = operands.split(', ')
% end
%
% def get_ctor_args(insn)
%     operands = get_operands(insn.sig)
%     ops = Array.new
%     ctor_args = Array.new
%     imms = Array.new
%     imm_members = Array.new
%     ids = Array.new
%     id_members = Array.new
%     regs = Array.new
%     reg_members = Array.new
%     id_count = 0
%     operands.map do |operand|
%     operand_parts = operand.split(':')
%       case operand_parts.size
%       when 1
%         name = operand_parts[0]
%       when 2
%         name, _ = operand_parts
%       when 3
%         name, _, _ = operand_parts
%       else
%         raise 'Unexpected operand string'
%       end
%
%       name_tmp = name.to_s.gsub(/[0-9]/, '').to_sym;
%
%       if is_Id(name_tmp)
%           name = "stringId_"
%           name.concat(id_count.to_s)
%           id_count = id_count + 1
%       end
%       type = get_operand_type(name, name_tmp, insn)
%       if type == "std::string"
%           ids.push(name)
%           id_members.push("#{type} #{name}")
%       elsif type == "uint16_t"
%           regs.push(name)
%           reg_members.push("#{type} #{name}")
%       else
%           imms.push(name)
%           imm_members.push("#{type} #{name}")
%       end
%       ctor_args.push("#{type} #{name}")
%     end
%     ops = regs + imms + ids
%     members = reg_members + imm_members + id_members
%     return ops,ctor_args,members,ids,imms,regs
% end
%
/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Autogenerated file -- DO NOT EDIT!

#ifndef ASSEMBLER_ASSEMBLY_INS_H
#define ASSEMBLER_ASSEMBLY_INS_H

#include <array>
#include <string>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>

#include "assembly-debug.h"
#include "bytecode_emitter.h"
#include "file_items.h"
#include "isa.h"
#include "lexer.h"

namespace panda::pandasm {
class Ins;
using IType = std::variant<int64_t, double>;
using InsPtr = std::unique_ptr<pandasm::Ins>;

enum class Opcode {
#define OPLIST(opcode, name, optype, width, flags, def_idx, use_idxs) opcode,
    PANDA_INSTRUCTION_LIST(OPLIST)
#undef OPLIST
        INVALID,
    NUM_OPCODES = INVALID
};

enum InstFlags {
    NONE = 0,
    JUMP = (1U << 0U),
    COND = (1U << 1U),
    CALL = (1U << 2U),
    RETURN = (1U << 3U),
    ACC_READ = (1U << 4U),
    ACC_WRITE = (1U << 5U),
    PSEUDO = (1U << 6U),
    THROWING = (1U << 7U),
    METHOD_ID = (1U << 8U),
    FIELD_ID = (1U << 9U),
    TYPE_ID = (1U << 10U),
    STRING_ID = (1U << 11U),
    LITERALARRAY_ID = (1U << 12U),
    CALL_RANGE = (1U << 13U),
};

constexpr int INVALID_REG_IDX = -1;

constexpr size_t MAX_NUMBER_OF_SRC_REGS = 4;  // TODO(mbolshov): auto-generate

constexpr InstFlags operator|(InstFlags a, InstFlags b)
{
    using utype = std::underlying_type_t<InstFlags>;
    return static_cast<InstFlags>(static_cast<utype>(a) | static_cast<utype>(b));
}

#define OPLIST(opcode, name, optype, width, flags, def_idx, use_idxs) flags,
constexpr std::array<unsigned, static_cast<size_t>(Opcode::NUM_OPCODES)> INST_FLAGS_TABLE = {
    PANDA_INSTRUCTION_LIST(OPLIST)};
#undef OPLIST

#define OPLIST(opcode, name, optype, width, flags, def_idx, use_idxs) width,
constexpr std::array<size_t, static_cast<size_t>(Opcode::NUM_OPCODES)> INST_WIDTH_TABLE = {
    PANDA_INSTRUCTION_LIST(OPLIST)};
#undef OPLIST

#define OPLIST(opcode, name, optype, width, flags, def_idx, use_idxs) def_idx,
constexpr std::array<int, static_cast<size_t>(Opcode::NUM_OPCODES)> DEF_IDX_TABLE = {PANDA_INSTRUCTION_LIST(OPLIST)};
#undef OPLIST

#define OPLIST(opcode, name, optype, width, flags, def_idx, use_idxs) use_idxs,
// clang-format off
constexpr std::array<std::array<int, MAX_NUMBER_OF_SRC_REGS>,
                     static_cast<size_t>(Opcode::NUM_OPCODES)> USE_IDXS_TABLE = {PANDA_INSTRUCTION_LIST(OPLIST)};
// clang-format on
#undef OPLIST

class Ins {
public:
    std::string ToString(std::string endline = "", bool print_args = false, size_t first_arg_idx = 0) const;
    virtual std::string OpcodeToString() const
    {
        return "";
    }

    bool HasFlag(InstFlags flag) const
    {
        if (opcode == Opcode::INVALID) {  // TODO(mbolshov): introduce 'label' opcode for labels
            return false;
        }
        return (INST_FLAGS_TABLE[static_cast<size_t>(opcode)] & flag) != 0;
    }

    bool CanThrow() const
    {
        return HasFlag(InstFlags::THROWING) || HasFlag(InstFlags::METHOD_ID) || HasFlag(InstFlags::FIELD_ID) ||
               HasFlag(InstFlags::TYPE_ID) || HasFlag(InstFlags::STRING_ID);
    }

    bool IsJump() const
    {
        return HasFlag(InstFlags::JUMP);
    }

    bool IsConditionalJump() const
    {
        return IsJump() && HasFlag(InstFlags::COND);
    }

    bool IsCall() const
    {  // Non-range call
        return HasFlag(InstFlags::CALL);
    }

    bool IsCallRange() const
    {  // Range call
        return HasFlag(InstFlags::CALL_RANGE);
    }

    bool IsPseudoCall() const
    {
        return HasFlag(InstFlags::PSEUDO) && HasFlag(InstFlags::CALL);
    }

    bool IsReturn() const
    {
        return HasFlag(InstFlags::RETURN);
    }

    size_t MaxRegEncodingWidth() const
    {
        if (opcode == Opcode::INVALID) {
            return 0;
        }
        return INST_WIDTH_TABLE[static_cast<size_t>(opcode)];
    }

    size_t OperandListLength() const
    {
        return Regs().size() + Ids().size() + Imms().size();
    }

    bool IsValidToEmit() const
    {
        const auto INVALID_REG_NUM = 1U << MaxRegEncodingWidth();
        for (auto reg : Regs()) {
            if (reg >= INVALID_REG_NUM) {
                return false;
            }
        }
        return true;
    }

    bool HasDebugInfo() const
    {
        return ins_debug.line_number != 0;
    }

    virtual std::vector<std::string> Ids() const
    {
        return {};
    }

    virtual std::vector<std::uint16_t> Regs() const
    {
        return {};
    }

    virtual std::vector<IType> Imms() const
    {
        return {};
    }

    virtual bool IsLabel() const
    {
        return false;
    }

    virtual const std::string &Label() const
    {
        return EMPTY_STRING;
    }

% max_reg_cnt = 0
% max_imm_cnt = 0
% max_id_cnt = 0
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% insn = group.first
% ops_list,ctor_arg_list,member_list,id_list,imm_list,reg_list = get_ctor_args(insn)
% max_reg_cnt = max_reg_cnt > reg_list.length ? max_reg_cnt : reg_list.length
% max_imm_cnt = max_imm_cnt > imm_list.length ? max_imm_cnt : imm_list.length
% max_id_cnt = max_id_cnt > id_list.length ? max_id_cnt : id_list.length
% end
    std::string &GetId(size_t idx)
    {
        switch(idx) {
% max_id_cnt.times do |i|
            case <%= i %>: 
                return GetId<%= i %>();
% end
            default:
                UNREACHABLE();
        }
    }

    const std::string &GetId(size_t idx) const
    {
        switch(idx) {
% max_id_cnt.times do |i|
            case <%= i %>: 
                return GetId<%= i %>();
% end
            default:
                UNREACHABLE();
        }
    }

    void SetId(size_t idx, const std::string &value)
    {
        switch(idx) {
% max_id_cnt.times do |i|
            case <%= i %>: 
                return SetId<%= i %>(value);
% end
            default:
                UNREACHABLE();
        }
    }

    std::uint16_t GetReg(size_t idx) const
    {
        switch(idx) {
% max_reg_cnt.times do |i|
            case <%= i %>: 
                return GetReg<%= i %>();
% end
            default:
                UNREACHABLE();
        }
    }

    void SetReg(size_t idx, const uint16_t value)
    {
        switch(idx) {
% max_reg_cnt.times do |i|
            case <%= i %>: 
                return SetReg<%= i %>(value);
% end
            default:
                UNREACHABLE();
        }
    }

    IType GetImm(size_t idx) const
    {
        switch(idx) {
% max_imm_cnt.times do |i|
            case <%= i %>: 
                return GetImm<%= i %>();
% end
            default:
                UNREACHABLE();
        }
    }

    void SetImm(size_t idx, const IType &value)
    {
        switch(idx) {
% max_imm_cnt.times do |i|
            case <%= i %>: 
                return SetImm<%= i %>(value);
% end
            default:
                UNREACHABLE();
        }
    }

    static bool Emit(BytecodeEmitter &emitter, const InsPtr &ins, panda_file::MethodItem *method,
                     const std::unordered_map<std::string, panda_file::BaseMethodItem *> &methods,
                     const std::unordered_map<std::string, panda_file::BaseFieldItem *> &fields,
                     const std::unordered_map<std::string, panda_file::BaseClassItem *> &classes,
                     const std::unordered_map<std::string, panda_file::StringItem *> &strings,
                     const std::unordered_map<std::string, panda_file::LiteralArrayItem *> &literalarrays,
                     const std::unordered_map<std::string_view, panda::Label>& labels);

    Ins *DeepCopy()
    {
        ASSERT(!IsLabel());
        auto res = Ins::CreateIns(opcode, Regs(), Imms(), Ids());
        res->ins_debug = ins_debug;
        return res;
    }

    template <typename... Args>
    static Ins *CreateIns(Opcode opcode, Args &&... args);

    virtual ~Ins() = default;

    debuginfo::Ins ins_debug;
    Opcode opcode;

protected:
    explicit Ins(Opcode opcode) : opcode(opcode) {}

private:
% max_reg_cnt.times do |i|
    virtual void SetReg<%= i %>(const uint16_t reg)
    {
        UNREACHABLE();
    }

    virtual uint16_t GetReg<%= i %>() const
    {
        UNREACHABLE();
    }

% end
%
% max_imm_cnt.times do |i|
    virtual void SetImm<%= i %>(const IType &imm)
    {
        UNREACHABLE();
    }

    virtual IType GetImm<%= i %>() const
    {
        UNREACHABLE();
    }
% end
%
% max_id_cnt.times do |i|
    virtual void SetId<%= i %>(const std::string &id)
    {
        UNREACHABLE();
    }

    virtual std::string &GetId<%= i %>()
    {
        UNREACHABLE();
    }

    virtual const std::string &GetId<%= i %>() const
    {
        UNREACHABLE();
    }

% end
    static const std::string EMPTY_STRING;
    std::string OperandsToString(bool print_args = false, size_t first_arg_idx = 0) const;
    std::string RegsToString(bool &first, bool print_args = false, size_t first_arg_idx = 0) const;
    std::string ImmsToString(bool &first) const;
    std::string IdsToString(bool &first) const;

    std::string IdToString(size_t idx, bool is_first) const;
    std::string ImmToString(size_t idx, bool is_first) const;
    std::string RegToString(size_t idx, bool is_first, bool print_args = false, size_t first_arg_idx = 0) const;
};

class LabelIns : public Ins {
public:
    LabelIns(std::string label) : Ins(Opcode::INVALID), label_(label) {}

    bool IsLabel() const override
    {
        return true;
    }

    const std::string &Label() const override
    {
        return label_;
    }

    std::string OpcodeToString() const override
    {
        return label_ + ": ";
    }

private:
    std::string label_;
};

% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% insn = group.first
% node_kind = get_node_kind(mnemonic)
% class_name = insn2node(insn)
% base_class = "Ins"
% ops_list,ctor_arg_list,member_list,id_list,imm_list,reg_list = get_ctor_args(insn)
% ctor_args = ctor_arg_list.map {|arg| "#{arg}"}.join(", ")
% members = member_list.map {|member| "#{member}_;"}.join("\n    ")
% ids = id_list.map{|id| "#{id}_"}.join(", ")
% imms = imm_list.map{|imm| "#{imm}_"}.join(", ")
% regs = reg_list.map{|reg| "#{reg}_"}.join(", ")
% id_index = imm_index = reg_index = -1
% init = ""
% if id_list.length != 0
%   init += "\n        " + id_list.map {|id| id_index+=1; "#{id}_ = ids[#{id_index}];"}.join("\n        ")
% end
% if imm_list.length != 0
%   init += "\n        " + imm_list.map {|imm| imm_index+=1; "#{imm}_ = std::holds_alternative<int64_t>(imms[#{imm_index}]) ? std::get<int64_t>(imms[#{imm_index}]) : std::get<double>(imms[#{imm_index}]);"}.join("\n        ")
% end
% if reg_list.length != 0
%   init += "\n        " + reg_list.map {|reg| reg_index+=1; "#{reg}_ = regs[#{reg_index}];"}.join("\n        ")
% end
% ops = (ops_list.length == 0 ? "" : ", ") + ops_list.map { |op| "#{op}_(#{op})"}.join(", ")
class <%= class_name %> : public <%= base_class %> {
public:
    <%= class_name %>(<%= ctor_args %>) : <%= base_class %>(Opcode::<%= insn.asm_token %>)<%= ops %> {}
    <%= class_name %>(std::vector<std::uint16_t> &regs, std::vector<IType> &imms, std::vector<std::string> &ids) : <%= base_class %>(Opcode::<%= insn.asm_token %>)
    {
        ASSERT(regs.size() >= <%= reg_index + 1 %>);
        ASSERT(ids.size() >= <%= id_index + 1 %>);
        ASSERT(imms.size() >= <%= imm_index + 1 %>);
        <%= init %>
    }
    <%= class_name %>(std::vector<std::uint16_t> &&regs, std::vector<IType> &&imms, std::vector<std::string> &&ids) : <%= base_class %>(Opcode::<%= insn.asm_token %>)
    {
        ASSERT(regs.size() >= <%= reg_index + 1 %>);
        ASSERT(ids.size() >= <%= id_index + 1 %>);
        ASSERT(imms.size() >= <%= imm_index + 1 %>);
        <%= init %>
    }

    std::string OpcodeToString() const override
    {
        return "<%= insn.mnemonic%>";
    }

    std::vector<std::uint16_t> Regs() const override
    {
        return {<%= regs %>};
    }

    std::vector<IType> Imms() const override
    {
        return {<%= imms %>};
    }

    std::vector<std::string> Ids() const override
    {
        return {<%=ids %>};
    }

% if ops_list.length != 0
private:
    <%= members %>
% idx = 0
% reg_list.each do |reg|
    void SetReg<%= idx %>(const uint16_t reg) override
    {
        <%= reg %>_ = reg;
    }

    std::uint16_t GetReg<%= idx %>() const override
    {
        return <%= reg %>_;
    }

% idx += 1
% end
%
% idx = 0
% imm_list.each do |imm|
    void SetImm<%= idx %>(const IType &imm) override
    {
        <%= imm %>_ = std::holds_alternative<int64_t>(imm) ? std::get<int64_t>(imm) : std::get<double>(imm);
    }

    IType GetImm<%= idx %>() const override
    {
        return <%= imm %>_;
    }

% idx += 1
% end
%
% idx = 0
% id_list.each do |id|
    void SetId<%= idx %>(const std::string &id) override
    {
        <%= id %>_ = id;
    }

    std::string &GetId<%= idx %>() override
    {
        return <%= id %>_;
    }

    const std::string &GetId<%= idx %>() const override
    {
        return <%= id %>_;
    }

% idx += 1
% end
% end
};
% end

template <typename... Args>
Ins *Ins::CreateIns(Opcode opcode, Args &&... args)
{
    switch(opcode) {
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% insn = group.first
% class_name = insn2node(insn)
        case Opcode::<%= insn.asm_token %>: {
            return new <%= class_name %>(std::forward<Args>(args)...);
        }
% end
        default:
            UNREACHABLE();
    }
}

} // namespace panda::pandasm

#endif // ASSEMBLER_ASSEMBLY_INS_H